<!DOCTYPE html>
<html>
<!--
Copyright 2007 The Closure Library Authors. All Rights Reserved.

Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Unit Tests - goog.dom.Range</title>
<script src="../base.js"></script>
<script>
  goog.require('goog.dom');
  goog.require('goog.dom.Range');
  goog.require('goog.testing.dom');
  goog.require('goog.testing.jsunit');
  goog.require('goog.userAgent');
</script>
</head>
<body>
  <div id="test1">Text</div>
  <div id="test2">abc<br id="br">def</div>
  <div id="empty"></div>
  <div id="test3"><div></div></div>
  <div id="removeTest"><div>Text that<br/>will be deleted</div></div>
  <div id="surroundTest"></div>
  <div id="insertTest"></div>
  <div id="surroundWithNodesTest"></div>
  <div id="removePartialTest">012345</div>
  <table id="tableTest"><tr><td id="cell">1</td><td>2</td></tr></table>
  <div id="ulTest"><ul><li>1</li><li>2</li></ul></div>
  <div id="olTest"><ol><li>1</li><li>2</li></ol></div>
  <img id="logo" src="http://www.google.com/intl/en_ALL/images/logo.gif">
  <div id="removeNodeTest"><div>Will be removed</div></div>

  <div id='bug1480638'></div>
  <div id='textWithSpaces'>hello world !</div>
  <div contentEditable=true>
  <div id='rangeAroundBreaks'>abcd<br />e</div>
  <div id='breaksAroundNode'><br />abcde<br /></div>
  </div>

  <!-- A focusable element to help restore focus to a sane state. -->
  <input id="focusableElement">

<script>
  var assertRangeEquals = goog.testing.dom.assertRangeEquals;

  function setUp() {
    // Reset the focus; some tests may invalidate the focus to exercise various
    // browser bugs.
    var focusableElement = goog.dom.getElement('focusableElement');
    focusableElement.focus();
    focusableElement.blur();
  }

  function normalizeHtml(str) {
    return str.toLowerCase().replace(/[\n\r\f"]/g, '')
        .replace(/<\/li>/g, ''); // " for emacs
  }

  function testCreate() {
    assertNotNull('Browser range object can be created for node',
        goog.dom.Range.createFromNodeContents(goog.dom.getElement('test1')));
  }

  function testTableRange() {
    var tr = goog.dom.getElement('cell').parentNode;
    var range = goog.dom.Range.createFromNodeContents(tr);
    assertEquals('Selection should have correct text', '12',
        range.getText());
    assertEquals('Selection should have correct html fragment',
        '1</td><td>2', normalizeHtml(range.getHtmlFragment()));

    // TODO(robbyw): On IE the TR is included, on FF it is not.
    //assertEquals('Selection should have correct valid html',
    //    '<tr id=row><td>1</td><td>2</td></tr>',
    //    normalizeHtml(range.getValidHtml()));

    assertEquals('Selection should have correct pastable html',
        '<table><tbody><tr><td id=cell>1</td><td>2</td></tr></tbody></table>',
        normalizeHtml(range.getPastableHtml()));
  }

  function testUnorderedListRange() {
    var ul = goog.dom.getElement('ulTest').firstChild;
    var range = goog.dom.Range.createFromNodeContents(ul);
    assertEquals('Selection should have correct html fragment',
        '1<li>2', normalizeHtml(range.getHtmlFragment()));

    // TODO(robbyw): On IE the UL is included, on FF it is not.
    //assertEquals('Selection should have correct valid html',
    //    '<li>1</li><li>2</li>', normalizeHtml(range.getValidHtml()));

    assertEquals('Selection should have correct pastable html',
        '<ul><li>1<li>2</ul>',
        normalizeHtml(range.getPastableHtml()));
  }

  function testOrderedListRange() {
    var ol = goog.dom.getElement('olTest').firstChild;
    var range = goog.dom.Range.createFromNodeContents(ol);
    assertEquals('Selection should have correct html fragment',
        '1<li>2', normalizeHtml(range.getHtmlFragment()));

    // TODO(robbyw): On IE the OL is included, on FF it is not.
    //assertEquals('Selection should have correct valid html',
    //    '<li>1</li><li>2</li>', normalizeHtml(range.getValidHtml()));

    assertEquals('Selection should have correct pastable html',
        '<ol><li>1<li>2</ol>',
        normalizeHtml(range.getPastableHtml()));
  }

  function testCreateFromNodes() {
    var start = goog.dom.getElement('test1').firstChild;
    var end = goog.dom.getElement('br');
    var range = goog.dom.Range.createFromNodes(start, 2, end, 0);
    assertNotNull('Browser range object can be created for W3C node range',
        range);

    assertEquals('Start node should be selected at start endpoint', start,
        range.getStartNode());
    assertEquals('Selection should start at offset 2', 2,
        range.getStartOffset());
    assertEquals('Start node should be selected at anchor endpoint', start,
        range.getAnchorNode());
    assertEquals('Selection should be anchored at offset 2', 2,
        range.getAnchorOffset());

    var div = goog.dom.getElement('test2');
    assertEquals('DIV node should be selected at end endpoint', div,
        range.getEndNode());
    assertEquals('Selection should end at offset 1', 1, range.getEndOffset());
    assertEquals('DIV node should be selected at focus endpoint', div,
        range.getFocusNode());
    assertEquals('Selection should be focused at offset 1', 1,
        range.getFocusOffset());


    assertTrue('Text content should be "xt\\s*abc"',
        /xt\s*abc/.test(range.getText()));
    assertFalse('Nodes range is not collapsed', range.isCollapsed());
  }


  function testCreateControlRange() {
    if (!goog.userAgent.IE) {
      return;
    }
    var cr = document.body.createControlRange();
    cr.addElement(goog.dom.getElement('logo'));

    var range = goog.dom.Range.createFromBrowserRange(cr);
    assertNotNull('Control range object can be created from browser range',
        range);
    assertEquals('Created range is a control range', goog.dom.RangeType.CONTROL,
        range.getType());
  }


  function testTextNode() {
    var range = goog.dom.Range.createFromNodeContents(
        goog.dom.getElement('test1').firstChild);

    assertEquals('Created range is a text range', goog.dom.RangeType.TEXT,
        range.getType());
    assertEquals('Text node should be selected at start endpoint', 'Text',
        range.getStartNode().nodeValue);
    assertEquals('Selection should start at offset 0', 0,
        range.getStartOffset());

    assertEquals('Text node should be selected at end endpoint', 'Text',
        range.getEndNode().nodeValue);
    assertEquals('Selection should end at offset 4', 'Text'.length,
        range.getEndOffset());

    assertEquals('Container should be text node', goog.dom.NodeType.TEXT,
        range.getContainer().nodeType);

    assertEquals('Text content should be "Text"', 'Text', range.getText());
    assertFalse('Text range is not collapsed', range.isCollapsed());
  }


  function testDiv() {
    var range = goog.dom.Range.createFromNodeContents(
        goog.dom.getElement('test2'));

    assertEquals('Text node "abc" should be selected at start endpoint', 'abc',
        range.getStartNode().nodeValue);
    assertEquals('Selection should start at offset 0', 0,
        range.getStartOffset());

    assertEquals('Text node "def" should be selected at end endpoint', 'def',
        range.getEndNode().nodeValue);
    assertEquals('Selection should end at offset 3', 'def'.length,
        range.getEndOffset());

    assertEquals('Container should be DIV', goog.dom.getElement('test2'),
        range.getContainer());

    assertTrue('Div text content should be "abc\\s*def"',
        /abc\s*def/.test(range.getText()));
    assertFalse('Div range is not collapsed', range.isCollapsed());
  }


  function testEmptyNode() {
    var range = goog.dom.Range.createFromNodeContents(
        goog.dom.getElement('empty'));

    assertEquals('DIV be selected at start endpoint',
        goog.dom.getElement('empty'), range.getStartNode());
    assertEquals('Selection should start at offset 0', 0,
        range.getStartOffset());

    assertEquals('DIV should be selected at end endpoint',
        goog.dom.getElement('empty'), range.getEndNode());
    assertEquals('Selection should end at offset 0', 0,
        range.getEndOffset());

    assertEquals('Container should be DIV', goog.dom.getElement('empty'),
        range.getContainer());

    assertEquals('Empty text content should be ""', '', range.getText());
    assertTrue('Empty range is collapsed', range.isCollapsed());
  }


  function testCollapse() {
    var range = goog.dom.Range.createFromNodeContents(
        goog.dom.getElement('test2'));
    assertFalse('Div range is not collapsed', range.isCollapsed());
    range.collapse();
    assertTrue('Div range is collapsed after call to empty()',
        range.isCollapsed());

    range = goog.dom.Range.createFromNodeContents(goog.dom.getElement('empty'));
    assertTrue('Empty range is collapsed', range.isCollapsed());
    range.collapse();
    assertTrue('Empty range is still collapsed', range.isCollapsed());
  }

  // TODO(robbyw): Test iteration over a strange document fragment.

  function testIterator() {
    goog.testing.dom.assertNodesMatch(goog.dom.Range.createFromNodeContents(
        goog.dom.getElement('test2')), ['abc', '#br', '#br', 'def']);
  }

  function testReversedNodes() {
    var node = goog.dom.getElement('test1').firstChild;
    var range = goog.dom.Range.createFromNodes(node, 4, node, 0);
    assertTrue('Range is reversed', range.isReversed());
    node = goog.dom.getElement('test3');
    range = goog.dom.Range.createFromNodes(node, 0, node, 1);
    assertFalse('Range is not reversed', range.isReversed());
  }

  function testReversedContents() {
    var range = goog.dom.Range.createFromNodeContents(
        goog.dom.getElement('test1'), true);
    assertTrue('Range is reversed', range.isReversed());
    assertEquals('Range should select "Text"', 'Text',
        range.getText());
    assertEquals('Range start offset should be 0', 0, range.getStartOffset());
    assertEquals('Range end offset should be 4', 4, range.getEndOffset());
    assertEquals('Range anchor offset should be 4', 4, range.getAnchorOffset());
    assertEquals('Range focus offset should be 0', 0, range.getFocusOffset());

    var range2 = range.clone();

    range.collapse(true);
    assertTrue('Range is collapsed', range.isCollapsed());
    assertFalse('Collapsed range is not reversed', range.isReversed());
    assertEquals('Post collapse start offset should be 4', 4,
        range.getStartOffset());

    range2.collapse(false);
    assertTrue('Range 2 is collapsed', range2.isCollapsed());
    assertFalse('Collapsed range 2 is not reversed', range2.isReversed());
    assertEquals('Post collapse start offset 2 should be 0', 0,
        range2.getStartOffset());
  }

  function testRemoveContents() {
    var outer = goog.dom.getElement('removeTest');
    var range = goog.dom.Range.createFromNodeContents(outer.firstChild);

    range.removeContents();

    assertEquals('Removed range content should be ""', '', range.getText());
    assertTrue('Removed range should be collapsed', range.isCollapsed());
    assertEquals('Outer div should have 1 child now', 1,
        outer.childNodes.length);
    assertEquals('Inner div should be empty', 0,
        outer.firstChild.childNodes.length);
  }

  function testRemovePartialContents() {
    var outer = goog.dom.getElement('removePartialTest');

    var range = goog.dom.Range.createFromNodes(outer.firstChild, 2,
        outer.firstChild, 4);
    removeHelper(1, range, outer, 1, '0145');

    range = goog.dom.Range.createFromNodes(outer.firstChild, 0,
        outer.firstChild, 1);
    removeHelper(2, range, outer, 1, '145');

    range = goog.dom.Range.createFromNodes(outer.firstChild, 2,
        outer.firstChild, 3);
    removeHelper(3, range, outer, 1, '14');

    var br = goog.dom.createDom('BR');
    outer.appendChild(br);
    range = goog.dom.Range.createFromNodes(outer.firstChild, 1,
        outer, 1);
    removeHelper(4, range, outer, 2, '1<br>');

    outer.innerHTML = '<br>123';
    range = goog.dom.Range.createFromNodes(outer, 0, outer.lastChild, 2);
    removeHelper(5, range, outer, 1, '3');

    outer.innerHTML = '123<br>456';
    range = goog.dom.Range.createFromNodes(outer.firstChild, 1, outer.lastChild,
        2);
    removeHelper(6, range, outer, 2, '16');

    outer.innerHTML = '123<br>456';
    range = goog.dom.Range.createFromNodes(outer.firstChild, 0, outer.lastChild,
        2);
    removeHelper(7, range, outer, 1, '6');

    outer.innerHTML = '<div></div>';
    range = goog.dom.Range.createFromNodeContents(outer.firstChild);
    removeHelper(8, range, outer, 1, '<div></div>');

    // TODO(robbyw): Fix the following edge cases:
    //    * Selecting contents of a node containing multiply empty divs
    //    * Selecting via createFromNodes(x, 0, x, x.childNodes.length)
    //    * Consistent handling of nodeContents(<div><div></div></div>).remove
  }

  function removeHelper(testNumber, range, outer, expectedChildCount,
      expectedContent) {
    range.removeContents();
    assertTrue(testNumber + ': Removed range should now be collapsed',
        range.isCollapsed());
    assertEquals(testNumber + ': Removed range content should be ""', '',
        range.getText());
    assertEquals(testNumber + ': Outer div should contain correct text',
        expectedContent, outer.innerHTML.toLowerCase());
    assertEquals(testNumber + ': Outer div should have ' + expectedChildCount +
        ' children now', expectedChildCount, outer.childNodes.length);
    assertNotNull(testNumber + ': Empty node should still exist',
        goog.dom.getElement('empty'));
  }

  function testSurroundContents() {
    var outer = goog.dom.getElement('surroundTest');
    outer.innerHTML = '---Text that<br/>will be surrounded---';
    var range = goog.dom.Range.createFromNodes(outer.firstChild, 3,
        outer.lastChild, outer.lastChild.nodeValue.length - 3);

    var div = goog.dom.createDom(goog.dom.TagName.DIV, {'style': 'color: red'});
    var output = range.surroundContents(div);

    assertEquals('Outer element should contain new element', outer,
        output.parentNode);
    assertFalse('New element should have no id', !!output.id);
    assertEquals('New element should be red', 'red', output.style.color);
    assertEquals('Outer element should have three children', 3,
        outer.childNodes.length);
    assertEquals('New element should have three children', 3,
        output.childNodes.length);

    // TODO(robbyw): Ensure the range stays in a reasonable state.
  }

  /**
   * Given two offsets into the 'foobar' node, make sure that inserting
   * nodes at those offsets doesn't change a selection of 'oba'.
   * @bug 1480638
   */
  function assertSurroundDoesntChangeSelectionWithOffsets(
      offset1, offset2, expectedHtml) {
    var div = goog.dom.getElement('bug1480638');
    div.innerHTML = 'foobar';
    var rangeToSelect = goog.dom.Range.createFromNodes(
        div.firstChild, 2, div.firstChild, 5);
    rangeToSelect.select();

    var rangeToSurround = goog.dom.Range.createFromNodes(
        div.firstChild, offset1, div.firstChild, offset2);
    rangeToSurround.surroundWithNodes(goog.dom.createDom('span'),
        goog.dom.createDom('span'));

    // Make sure that the selection didn't change.
    assertHTMLEquals('Selection must not change when contents are surrounded.',
        expectedHtml, goog.dom.Range.createFromWindow().getHtmlFragment());
  }

  function testSurroundWithNodesDoesntChangeSelection1() {
    assertSurroundDoesntChangeSelectionWithOffsets(3, 4,
        'o<span></span>b<span></span>a');
  }

  function testSurroundWithNodesDoesntChangeSelection2() {
    assertSurroundDoesntChangeSelectionWithOffsets(3, 6,
        'o<span></span>ba');
  }

  function testSurroundWithNodesDoesntChangeSelection3() {
    assertSurroundDoesntChangeSelectionWithOffsets(1, 3,
        'o<span></span>ba');
  }

  function testSurroundWithNodesDoesntChangeSelection4() {
    assertSurroundDoesntChangeSelectionWithOffsets(1, 6,
        'oba');
  }

  function testInsertNode() {
    var outer = goog.dom.getElement('insertTest');
    outer.innerHTML = 'ACD';

    var range = goog.dom.Range.createFromNodes(outer.firstChild, 1,
        outer.firstChild, 2);
    range.insertNode(goog.dom.createTextNode('B'), true);
    assertEquals('Element should have correct innerHTML', 'ABCD',
        outer.innerHTML);

    outer.innerHTML = '12';
    range = goog.dom.Range.createFromNodes(outer.firstChild, 0,
        outer.firstChild, 1);
    var br = range.insertNode(goog.dom.createDom(goog.dom.TagName.BR), false);
    assertEquals('New element should have correct innerHTML', '1<br>2',
        outer.innerHTML.toLowerCase());
    assertEquals('BR should be in outer', outer, br.parentNode);
  }

  function testReplaceContentsWithNode() {
    var outer = goog.dom.getElement('insertTest');
    outer.innerHTML = 'AXC';

    var range = goog.dom.Range.createFromNodes(outer.firstChild, 1,
        outer.firstChild, 2);
    range.replaceContentsWithNode(goog.dom.createTextNode('B'));
    assertEquals('Element should have correct innerHTML', 'ABC',
        outer.innerHTML);

    outer.innerHTML = 'ABC';
    range = goog.dom.Range.createFromNodes(outer.firstChild, 3,
        outer.firstChild, 3);
    range.replaceContentsWithNode(goog.dom.createTextNode('D'));
    assertEquals(
        'Element should have correct innerHTML after collapsed replace',
        'ABCD', outer.innerHTML);

    outer.innerHTML = 'AX<b>X</b>XC';
    range = goog.dom.Range.createFromNodes(outer.firstChild, 1,
        outer.lastChild, 1);
    range.replaceContentsWithNode(goog.dom.createTextNode('B'));
    goog.testing.dom.assertHtmlContentsMatch('ABC', outer);
  }

  function testSurroundWithNodes() {
    var outer = goog.dom.getElement('insertTest');
    outer.innerHTML = 'ACE';
    var range = goog.dom.Range.createFromNodes(outer.firstChild, 1,
        outer.firstChild, 2);

    range.surroundWithNodes(goog.dom.createTextNode('B'),
        goog.dom.createTextNode('D'));

    assertEquals('New element should have correct innerHTML', 'ABCDE',
        outer.innerHTML);
  }

  function testIsRangeInDocument() {
    var outer = goog.dom.getElement('insertTest');
    outer.innerHTML = '<br>ABC';
    var range = goog.dom.Range.createCaret(outer.lastChild, 1);

    assertEquals('Should get correct start element', 'ABC',
        range.getStartNode().nodeValue);
    assertTrue('Should be considered in document', range.isRangeInDocument());

    outer.innerHTML = 'DEF';

    assertFalse('Should be marked as out of document',
        range.isRangeInDocument());
  }

  function testRemovedNode() {
    var node = goog.dom.getElement('removeNodeTest');
    var range = goog.dom.browserrange.createRangeFromNodeContents(node);
    range.select();
    goog.dom.removeNode(node);

    var newRange = goog.dom.Range.createFromWindow(window);

    // In Chrome 14 and below (<= Webkit 535.1), newRange will be null.
    // In Chrome 16 and above (>= Webkit 535.7), newRange will be collapsed
    // like on other browsers.
    // We didn't bother testing in between.
    if (goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('535.7')) {
      assertNull('Webkit supports rangeCount == 0', newRange);
    } else {
      assertTrue('The other browsers will just have an empty range.',
          newRange.isCollapsed());
    }
  }

  function testReversedRange() {
    goog.dom.Range.createFromNodes(goog.dom.getElement('test2'), 0,
        goog.dom.getElement('test1'), 0).select();

    var range = goog.dom.Range.createFromWindow(window);
    assertTrue('Range should be reversed',
        goog.userAgent.IE || range.isReversed());
  }

  function testUnreversedRange() {
    goog.dom.Range.createFromNodes(goog.dom.getElement('test1'), 0,
        goog.dom.getElement('test2'), 0).select();

    var range = goog.dom.Range.createFromWindow(window);
    assertFalse('Range should not be reversed', range.isReversed());
  }

  function testReversedThenUnreversedRange() {
    // This tests a workaround for a webkit bug where webkit caches selections
    // incorrectly.
    goog.dom.Range.createFromNodes(goog.dom.getElement('test2'), 0,
        goog.dom.getElement('test1'), 0).select();
    goog.dom.Range.createFromNodes(goog.dom.getElement('test1'), 0,
        goog.dom.getElement('test2'), 0).select();

    var range = goog.dom.Range.createFromWindow(window);
    assertFalse('Range should not be reversed', range.isReversed());
  }

  function testHasAndClearSelection() {
    goog.dom.Range.createFromNodeContents(
        goog.dom.getElement('test1')).select();

    assertTrue('Selection should exist', goog.dom.Range.hasSelection());

    goog.dom.Range.clearSelection();

    assertFalse('Selection should not exist', goog.dom.Range.hasSelection());
  }

  function assertForward(string, startNode, startOffset, endNode, endOffset) {
    var root = goog.dom.getElement('test2');
    var originalInnerHtml = root.innerHTML;

    assertFalse(string, goog.dom.Range.isReversed(startNode, startOffset,
        endNode, endOffset));
    assertTrue(string, goog.dom.Range.isReversed(endNode, endOffset,
        startNode, startOffset));
    assertEquals('Contents should be unaffected after: ' + string,
        root.innerHTML, originalInnerHtml);
  }

  function testIsReversed() {
    var root = goog.dom.getElement('test2');
    var text1 = root.firstChild; // Text content: 'abc'.
    var br = root.childNodes[1];
    var text2 = root.lastChild; // Text content: 'def'.

    assertFalse('Same element position gives false', goog.dom.Range.isReversed(
        root, 0, root, 0));
    assertFalse('Same text position gives false', goog.dom.Range.isReversed(
        text1, 0, text2, 0));
    assertForward('Element offsets should compare against each other',
        root, 0, root, 2);
    assertForward('Text node offsets should compare against each other',
        text1, 0, text2, 2);
    assertForward('Text nodes should compare correctly',
        text1, 0, text2, 0);
    assertForward('Text nodes should compare to later elements',
        text1, 0, br, 0);
    assertForward('Text nodes should compare to earlier elements',
        br, 0, text2, 0);
    assertForward('Parent is before element child', root, 0, br, 0);
    assertForward('Parent is before text child', root, 0, text1, 0);
    assertFalse('Equivalent position gives false', goog.dom.Range.isReversed(
        root, 0, text1, 0));
    assertFalse('Equivalent position gives false', goog.dom.Range.isReversed(
        root, 1, br, 0));
    assertForward('End of element is after children', text1, 0, root, 3);
    assertForward('End of element is after children', br, 0, root, 3);
    assertForward('End of element is after children', text2, 0, root, 3);
    assertForward('End of element is after end of last child',
        text2, 3, root, 3);
  }

  function testSelectAroundSpaces() {
    // set the selection
    var textNode = goog.dom.getElement('textWithSpaces').firstChild;
    goog.dom.TextRange.createFromNodes(
        textNode, 5, textNode, 12).select();

    // get the selection and check that it matches what we set it to
    var range = goog.dom.Range.createFromWindow();
    assertEquals(' world ', range.getText());
    assertEquals(5, range.getStartOffset());
    assertEquals(12, range.getEndOffset());
    assertEquals(textNode, range.getContainer());

    // Check the contents again, because there used to be a bug where
    // it changed after calling getContainer().
    assertEquals(' world ', range.getText());
  }

  function testSelectInsideSpaces() {
    // set the selection
    var textNode = goog.dom.getElement('textWithSpaces').firstChild;
    goog.dom.TextRange.createFromNodes(
        textNode, 6, textNode, 11).select();

    // get the selection and check that it matches what we set it to
    var range = goog.dom.Range.createFromWindow();
    assertEquals('world', range.getText());
    assertEquals(6, range.getStartOffset());
    assertEquals(11, range.getEndOffset());
    assertEquals(textNode, range.getContainer());

    // Check the contents again, because there used to be a bug where
    // it changed after calling getContainer().
    assertEquals('world', range.getText());
  }

  function testRangeBeforeBreak() {
    var container = goog.dom.getElement('rangeAroundBreaks');
    var text = container.firstChild;
    var offset = text.length;
    assertEquals(4, offset);

    var br = container.childNodes[1];
    var caret = goog.dom.Range.createCaret(text, offset);
    caret.select();
    assertEquals(offset, caret.getStartOffset());

    var range = goog.dom.Range.createFromWindow();
    assertFalse('Should not contain whole <br>',
        range.containsNode(br, false));
    if (goog.userAgent.IE) {
      assertTrue('Range over <br> is adjacent to the immediate range before it',
          range.containsNode(br, true));
    } else {
      assertFalse('Should not contain partial <br>',
          range.containsNode(br, true));
    }

    assertEquals(offset, range.getStartOffset());
    assertEquals(text, range.getStartNode());
  }

  function testRangeAfterBreak() {
    var container = goog.dom.getElement('rangeAroundBreaks');
    var br = container.childNodes[1];
    var caret = goog.dom.Range.createCaret(container.lastChild, 0);
    caret.select();
    assertEquals(0, caret.getStartOffset());

    var range = goog.dom.Range.createFromWindow();
    assertFalse('Should not contain whole <br>',
        range.containsNode(br, false));
    var isSafari3 = goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('528');

    if (goog.userAgent.IE || isSafari3) {
      assertTrue('Range over <br> is adjacent to the immediate range after it',
          range.containsNode(br, true));
    } else {
      assertFalse('Should not contain partial <br>',
          range.containsNode(br, true));
    }

    if (isSafari3) {
      assertEquals(2, range.getStartOffset());
      assertEquals(container, range.getStartNode());
    } else {
      assertEquals(0, range.getStartOffset());
      assertEquals(container.lastChild, range.getStartNode());
    }
  }

  function testRangeAtBreakAtStart() {
    var container = goog.dom.getElement('breaksAroundNode');
    var br = container.firstChild;
    var caret = goog.dom.Range.createCaret(container.firstChild, 0);
    caret.select();
    assertEquals(0, caret.getStartOffset());

    var range = goog.dom.Range.createFromWindow();
    assertTrue('Range over <br> is adjacent to the immediate range before it',
        range.containsNode(br, true));
    assertFalse('Should not contain whole <br>',
        range.containsNode(br, false));

    assertRangeEquals(container, 0, container, 0, range);
  }

  function testFocusedElementDisappears() {
    // This reproduces a failure case specific to Gecko, where an element is
    // created, contentEditable is set, is focused, and removed.  After that
    // happens, calling selection.collapse fails.
    // https://bugzilla.mozilla.org/show_bug.cgi?id=773137
    var disappearingElement = goog.dom.createDom('div');
    document.body.appendChild(disappearingElement);
    disappearingElement.contentEditable = true;
    disappearingElement.focus();
    document.body.removeChild(disappearingElement);
    var container = goog.dom.getElement('empty');
    var caret = goog.dom.Range.createCaret(container, 0);
    // This should not throw.
    caret.select();
    assertEquals(0, caret.getStartOffset());
  }

  function assertNodeEquals(expected, actual) {
    assertEquals(
        'Expected: ' + goog.testing.dom.exposeNode(expected) +
        '\nActual: ' + goog.testing.dom.exposeNode(actual),
        expected, actual);
  }
</script>
</body>
</html>
